package gitlet;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.DosFileAttributeView;

import static gitlet.Utils.*;

// TODO: any imports you need here

/** Represents a gitlet repository.
 *  这是一个用来初始化Gitlet仓库的类，其中包含了Gitlet文件结构的定义方式以及Gitlet操作的函数定义
 *  does at a high level.
 *
 *  @author MarkZhang
 */
public class Repository {
    /**
     * TODO: add instance variables here. there should be a variable called branch
     *
     * List all instance variables of the Repository class here with a useful
     * comment above them describing what that variable represents and how that
     * variable is used. We've provided two examples for you.
     */
    //the variable that shows the current branch
    public String branch;

    /** The current working directory. */
    public static final File CWD = new File(System.getProperty("user.dir"));
    /** The .gitlet directory. */
    public static final File GITLET_DIR = join(CWD, ".gitlet");

    //Init the repository
    public void init() {
        //create a new .gitlet directory
        if (GITLET_DIR.exists()) {
            System.out.println("A Gitlet version-control system already exists in the current directory.");
            return;
        } else {
            GITLET_DIR.mkdir();
            // set the visibility of the .gitlet directory to hidden
            DosFileAttributeView fileAttributeView = Files.getFileAttributeView(GITLET_DIR.toPath(), DosFileAttributeView.class);
            try {
                fileAttributeView.setHidden(true);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            //get the current time
            long time = System.currentTimeMillis();
            //change the type of the time to a string
            String timestamp = String.valueOf(time);
            //create the initial commit
            Commit initialCommit = new Commit("initial commit", null,timestamp, null);
            //set the branch to master by defult
            branch = "master";
            //create object area for commits areas
            File object = join(GITLET_DIR, "object");
            //create subdirectories for commits areas and blobs
            File commit = join(object, "commit");
            //create subdirectories for blobs areas
            File blob = join(object, "blob");
            //create head area for commits areas
            File head = join(GITLET_DIR, "HEAD");
            //init the head to master
            writeContents(head, "master");
            //create staging area for commits
            File staging = join(GITLET_DIR, "staging");
            //create the files above
            object.mkdir();
            commit.mkdir();
            blob.mkdir();
            staging.mkdir();
        }

    }
    //add a copy of the files as currently exist to the stagging area
    public void add(String filename) {
        //if the current working version of the file is identical to the version in the current commit do not stage!
        //if the file does not exist at all,print an error message
        if (!join(CWD, filename).exists()) {
            System.out.println("File does not exist.");
            return;
        }
        //if the current working version of the file is identical to the version in the current commit, do not stage it
        if (join(GITLET_DIR, "object", "commit", "HEAD", filename).exists()) {
            if (join(CWD, filename).equals(join(GITLET_DIR, "object", "commit", "HEAD", filename))) {
                return;
            }
        }
        //if the file is not staged, add it to the staging area
        if (!join(GITLET_DIR, "staging", filename).exists()) {
            writeContents(join(GITLET_DIR, "staging", filename), readContentsAsString(join(CWD, filename)));
        }
        //if the file is already staged, overwrite the file in the staging area with the new version
        else {
            writeContents(join(GITLET_DIR, "staging", filename), readContentsAsString(join(CWD, filename)));
        }
    }
    //commit the files in the staging area
    public void commit(String message) {
        //if no files have been staged, print an error message
        if (join(GITLET_DIR,"stageing").list().length == 1) {
            System.out.println("No changes added to the commit.");
            return;
        }
        //create a new commit object and set the parent commit
        //TODO: figure out how the blobs work
        Commit newCommit = new Commit(message, readObject(join(GITLET_DIR, "HEAD", "HEAD"), Commit.class), String.valueOf(System.currentTimeMillis()), null);
        //create a new commit file
        File commitFile = join(GITLET_DIR, "object", "commit", newCommit.getUID());
        //serialize the commit object and write it to the commit file
        writeObject(commitFile, newCommit);
        //update the head to the new commit
        writeContents(join(GITLET_DIR, "HEAD", "HEAD"), newCommit.getUID());
        //clear the staging area
        for (File file : join(GITLET_DIR, "staging").listFiles()) {
            file.delete();
        }
    }
    //remove the file from the staging area
    public void rm(String filename) {
        //if the file is not staged, print an error message
        if (!join(GITLET_DIR, "staging", filename).exists()) {
            System.out.println("No reason to remove the file.");
            return;
        }
        //remove the file from the staging area
        join(GITLET_DIR, "staging", filename).delete();
    }
    //print the commit history, but we only display the first parent commit links and ignore any second parent links
    public void log() {
        //get into the commit directory
        File commit = join(GITLET_DIR, "object", "commit");
        //get the current commit and deserialize it to a commit object
        File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD", "HEAD")));
        Commit current = readObject(currentCommit, Commit.class);
        //print the commit history
        while (current != null) {
            System.out.println("===");
            System.out.println("commit " + current.getUID());
            System.out.println("Date: " + current.getTimestamp());
            System.out.println(current.getMessage());
            System.out.println();
            current = current.getParent();//implement the circular of the commit messages
        }
    }
    //print the commit history of all commits
    public void global_log() {
        //get the global log of all commits
        File commit = join(GITLET_DIR, "object", "commit");
        for (File file : commit.listFiles()) {
            Commit current = readObject(file, Commit.class);
            System.out.println("===");
            System.out.println("commit " + current.getUID());
            System.out.println("Date: " + current.getTimestamp());
            System.out.println(current.getMessage());
            System.out.println();
        }
    }
    //find the commit with the given message
    public void find(String message) {
        //get the global log of all commits
        File commit = join(GITLET_DIR, "object", "commit");
        for (File file : commit.listFiles()) {
            Commit current = readObject(file, Commit.class);
            if (current.getMessage().equals(message)) {
                System.out.println(current.getUID());//print the commit message by line
                return;
            }
        }
    }
    //print the status of the repository
    public void status() {
        //get the current branch
        File head = join(GITLET_DIR, "HEAD");
        String currentBranch = readContentsAsString(head);
        //print the current branch
        System.out.println("=== Branches ===");
        System.out.println("*" + currentBranch);
        for (File file : join(GITLET_DIR, "object", "commit").listFiles()) {
            if (!file.getName().equals(currentBranch)) {
                System.out.println(file.getName());
            }
        }
        System.out.println();
        //print the staged files
        System.out.println("=== Staged Files ===");
        for (File file : join(GITLET_DIR, "staging").listFiles()) {
            System.out.println(file.getName());
        }
        System.out.println();
        //print the removed files
        System.out.println("=== Removed Files ===");
        for (File file : join(GITLET_DIR, "staging").listFiles()) {
            System.out.println(file.getName());
        }
        System.out.println();
        //print the modified files
        System.out.println("=== Modifications Not Staged For Commit ===");
        System.out.println();
        //print the untracked files
        System.out.println("=== Untracked Files ===");
        System.out.println();
    }
    //checkout the file from the current commit
    public void checkout(String filename) {
        //get the current commit
        File commit = join(GITLET_DIR, "object", "commit");
        File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD")));
        Commit current = readObject(currentCommit, Commit.class);
        //get the file from the current commit
        File file = join(GITLET_DIR, "object", "blob", current.getUID(), filename);
        //copy the file to the current working directory
        writeContents(join(CWD, filename), readContents(file));
    }
    //checkout the file from the commit id
    public void checkout(String commitID, String filename) {
        //get the commit from the commit id
        File commit = join(GITLET_DIR, "object", "commit");
        File currentCommit = join(commit, commitID);
        Commit current = readObject(currentCommit, Commit.class);
        //get the file from the commit
        File file = join(GITLET_DIR, "object", "blob", current.getUID(), filename);
        //copy the file to the current working directory
        writeContents(join(CWD, filename), readContents(file));
    }
    //checkout the branch
    public void checkoutBranch(String branchName) {
        //get the current branch
        File head = join(GITLET_DIR, "HEAD");
        String currentBranch = readContentsAsString(head);
        //if the branch does not exist, print an error message
        if (!join(GITLET_DIR, "object", "commit", branchName).exists()) {
            System.out.println("No such branch exists.");
            return;
        }
        //if the branch is the current branch, print an error message
        if (currentBranch.equals(branchName)) {
            System.out.println("Already on the target branch, no need to change the branch");
            return;
        }
        //get the current commit
        File commit = join(GITLET_DIR, "object", "commit");
        File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD")));
        Commit current = readObject(currentCommit, Commit.class);
        //get the branch commit
        File branchCommit = join(commit, branchName);
        Commit branch = readObject(branchCommit, Commit.class);
        //get the files from the branch commit
        for (File file : join(GITLET_DIR, "object", "blob", branch.getUID()).listFiles()) {
            //copy the file to the current working directory
            writeContents(join(CWD, file.getName()), readContents(file));
        }
        //delete the files that are not in the branch commit
        for (File file : join(CWD).listFiles()) {
            if (!join(GITLET_DIR, "object", "blob", branch.getUID(), file.getName()).exists()) {
                file.delete();
            }
        }
        //update the head to the branch commit
        writeContents(join(GITLET_DIR, "HEAD"), branchName);
    }
    //create a new branch Usage: java gitlet.Main branch [branch name]
    //
    //Description: Creates a new branch with the given name, and points it at the current head commit. A branch is nothing more than a name for a reference (a SHA-1 identifier) to a commit node. This command does NOT immediately switch to the newly created branch (just as in real Git). Before you ever call branch, your code should be running with a default branch called “master”.
    public void branch(String branchName) {
        //get the current branch
        File head = join(GITLET_DIR, "HEAD");
        String currentBranch = readContentsAsString(head);
        //if the branch already exists, print an error message
        if (join(GITLET_DIR, "object", "commit", branchName).exists()) {
            System.out.println("A branch with that name already exists.");
            return;
        }
        //create a new branch commit
        File commit = join(GITLET_DIR, "object", "commit");
        File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD")));
        Commit current = readObject(currentCommit, Commit.class);
        Commit newBranch = new Commit(current.getMessage(), current, String.valueOf(System.currentTimeMillis()), null);
        //create a new branch commit file
        File branchCommit = join(commit, branchName);
        //serialize the branch commit object and write it to the branch commit file
        writeObject(branchCommit, newBranch);
    }
    //remove the branch
    public void rm_branch(String branchName) {
        //get the current branch
        File head = join(GITLET_DIR, "HEAD");
        String currentBranch = readContentsAsString(head);
        //if the branch does not exist, print an error message
        if (!join(GITLET_DIR, "object", "commit", branchName).exists()) {
            System.out.println("A branch with that name does not exist.");
            return;
        }
        //if the branch is the current branch, print an error message
        if (currentBranch.equals(branchName)) {
            System.out.println("Cannot remove the current branch.");
            return;
        }
        //remove the branch
        join(GITLET_DIR, "object", "commit", branchName).delete();
    }
    //reset the commit header to the given commit
    public void reset(String commitID) {
        //get the commit from the commit id
        File commit = join(GITLET_DIR, "object", "commit");
        File currentCommit = join(commit, commitID);
        Commit current = readObject(currentCommit, Commit.class);
        //get the files from the commit
        for (File file : join(GITLET_DIR, "object", "blob", current.getUID()).listFiles()) {
            //copy the file to the current working directory
            writeContents(join(CWD, file.getName()), readContents(file));
        }
        //delete the files that are not in the commit
        for (File file : join(CWD).listFiles()) {
            if (!join(GITLET_DIR, "object", "blob", current.getUID(), file.getName()).exists()) {
                file.delete();
            }
        }
        //update the head to the commit
        writeContents(join(GITLET_DIR, "HEAD"), commitID);
    }
    //merge the branch with the current branch
    public void merge(String branchName) {
        //get the current branch
        File head = join(GITLET_DIR, "HEAD");
        String currentBranch = readContentsAsString(head);
        //if the branch does not exist, print an error message
        if (!join(GITLET_DIR, "object", "commit", branchName).exists()) {
            System.out.println("A branch with that name does not exist.");
            return;
        }
        //if the branch is the current branch, print an error message
        if (currentBranch.equals(branchName)) {
            System.out.println("Cannot merge a branch with itself.");
            return;
        }
        //get the current commit
        File commit = join(GITLET_DIR, "object", "commit");
        File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD")));
        Commit current = readObject(currentCommit, Commit.class);
        //get the branch commit
        File branchCommit = join(commit, branchName);
        Commit branch = readObject(branchCommit, Commit.class);
        //get the split point commit
        Commit splitPoint = findSplitPoint(current, branch);
        //get the files from the split point commit
        for (File file : join(GITLET_DIR, "object", "blob", splitPoint.getUID()).listFiles()) {
            //copy the file to the current working directory
            writeContents(join(CWD, file.getName()), readContents(file));
        }
        //get the files from the branch commit
        for (File file : join(GITLET_DIR, "object", "blob", branch.getUID()).listFiles()) {
            //if the file is not in the split point commit, copy the file to the current working directory
            if (!join(GITLET_DIR, "object", "blob", splitPoint.getUID(), file.getName()).exists()) {
                writeContents(join(CWD, file.getName()), readContents(file));
            }
        }
        //get the files from the current commit
        for (File file : join(GITLET_DIR, "object", "blob", current.getUID()).listFiles()) {
            //if the file is not in the split point commit, copy the file to the current working directory
            if (!join(GITLET_DIR, "object", "blob", splitPoint.getUID(), file.getName()).exists()) {
                writeContents(join(CWD, file.getName()), readContents(file));
            }
        }
    }

    private Commit findSplitPoint(Commit current, Commit branch) {
        //get the current commit
        Commit currentCommit = current;
        //get the branch commit
        Commit branchCommit = branch;
        //get the commit history of the current commit
        while (currentCommit != null) {
            //get the commit history of the branch commit
            while (branchCommit != null) {
                //if the current commit is the same as the branch commit, return the split point commit
                if (currentCommit.getUID().equals(branchCommit.getUID())) {
                    return currentCommit;
                }
                branchCommit = branchCommit.getParent();
            }
            currentCommit = currentCommit.getParent();
        }
        return null;
    }
}
